home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Collection of Tools & Utilities
/
Collection of Tools and Utilities.iso
/
edit
/
me_cd25.zip
/
DOC.ZIP
/
UNDO.ART
< prev
Wrap
Text File
|
1992-11-09
|
28KB
|
672 lines
-*-text-*-
Implementing Undo for Text Editors
------------ ---- --- ---- -------
Craig Durland
craig@cv.hp.com
Copyright 1991
February 1991
Revised
September 1991
February 1992
Probably anyone who has used a text editor has, at one time or another,
made a boo boo and immediately wished they could to go back in time and
not do it all over again. The Undo command gives that ability by moving
backwards in time, reversing the effects of the most recent changes to
the text. Some undo commands can go back to the beginning of time,
undoing all changes that have occurred to the text.
The undo described in this article is a subset of the idealized undo -
it only reverses changes to text. It doesn't keep track of cursor
movements, buffer changes or the zillions of other things we do while
editing text. While this can make undoing changes disconcerting because
it can jump from change to change differently from what you remember, it
also reduces the amount of stuff that the editor needs to remember
(saving memory) and helps keep the editor from bogging down trying to
keep track of whats been going on. Finally, the material presented is
for text editors. Editors of other types may find some of the
discussion relevant but that will be a happy accident.
Jonathan Payne (author of JOVE) has stated that "Undo/redo is trivial to
implement" (comp.editors, Tue, 2 Jan 1990). Having just implemented
undo for the editor I am working on, I think that straight forward is a
better term than trivial. Since it took me a long time to figure out a
workable undo scheme, I thought others might be interested in what I
did.
Caveats: The algorithms, code and data structures used in this article
are based on working and tested code that has been "sanitized" to remove
obscuring details (like what stack data structures I used). If you use
the code, make sure you understand it so that you can properly fill in
the missing details.
Conditions: You are free to use the algorithms, code and data
structures contained in this article in anyway you please. If you use
large chunks, I would appreciate an acknowledgment. If you distribute
this article, in whole or part, please retain the authorship notice.
What is Not Discussed
---- -- --- ---------
Redo is a undo for undo. Very handy if you undo one time to many and
need to undo that last undo. I haven't implemented it yet but I think
that it will be easy to modify the algorithms to support it. I also
think some dragons are there but they are hiding from me. We'll see
when I actually get around to doing it.
Editor Implementations and How They Affect Undo
------ --------------- --- --- ---- ------ ----
Two popular ways text editors store text are "buffer gap" and "linked
list of lines" (described in detail else where - see comp.editors). As
you might imagine, different implementations for storing text can affect
undo implementations. Fortunately for this article, the affects are
relatively minor. As I discuss data structures and algorithms, I will
point out the differences.
Note: I will refer to buffer gap editors as BGE and linked list editors
as LLE.
What You Need to Save to Implement Undo
---- --- ---- -- ---- -- --------- ----
I use Undo Events to represent text changes. These events are kept in a
undo stack, most recent event at the top of the stack. There is one
stack per text object (buffer in Emacs) so undo can only track changes
on a per buffer basis. This simplifies things quite a bit over tracking
all changes globally in a multibuffer editor.
I found that four event types seem to be enough describe all text
changes. They are:
- BU_INSERT: This event is used when text is inserted. The most common
case is when you type. When text is inserted, we need to remember how
much was inserted and where it was inserted. We don't need to
remember the what the text was because to undo this type of event, all
we have to do is delete the inserted text. Note that in order to
reduce the number of events saved (and memory used), you need to be
able to detect text that is inserted sequentially (like when you type
"123") so that all these events can packed into one event. This can
affect more than you would think at first glance and will be discussed
more fully else where.
- BU_DELETE: This event is used when text is deleted, for example when
you use the delete (or backspace) key. Here we need to remember the
place where the deletion took place and the text to be deleted. Note
that we have to save the text before (or while) it is deleted. This
is can cause trouble for some editor implementations. Again, event
packing can save much of space if users like to lean on the delete
key. To undo a delete, we have to insert the deleted text at the
place where it was deleted.
- BU_SP: A sequence point is a stopping point in the undo stack. It
tells the undo routine that this is the last event to undo so the user
will think that one of his actions has been undone. Note that this
implies that one user action may cause one or (many) more undo events
to be saved. This is especially true when the editor supports macros
or an extension language. I think that it is important that one press
of the undo key undoes one user action (there are a few exceptions
(like query replace)). Probably one of the hardest things to "get
right" (from the users point of view) is where to place sequence
points. Here is my list of rules for sequence points (take these with
a grain of salt - these are my opinions with nothing to back them up.
They may change.):
- Need sequence points so undoing stops at "natural, expected" places.
- Undo should stop where user explicitly marked the buffer as
unchanged (like save buffer to disk). These are usually UNCHANGED
events.
- A string of self insert keys should undo as one. Its pretty
annoying to have to hit undo 14 times to undo "this is a test".
- A word wrap or Return should break a character stream. Thus undo
only backs up a line at at time, probably better than undoing an
entire paragraph when only one line needed undoing.
- Commands, macros, programs written in the extension language, etc
should undo as a unit. ie if pressing a key caused a bunch of stuff
to happen, pressing undo should undo all the stuff the key caused.
- Since rules can't cover all cases (like query replace), programs
need to be able to add sequence points.
I also store the buffer modified state here so I know to mark the
buffer as unchanged if it was before this change happened.
- BU_UNCHANGED: This event marks a time when the text was marked as
unchanged or saved. Examples include saving the text to the disk.
When undoing, this event marks a stopping point: The user has undoed
back to a time when the buffer was "safe". When you make an
inadvertent change to text that you only wanted to look at, it is
psychologically reassuring to know the text is back to its original
state.
Only the most recent unchanged event actually indicates that the
buffer contents match those out on the disk (and every buffer change
before and after that event means the buffer is out of sync with the
disk). For undo, this means that only the most recent unchanged event
is valid - when that event is undone, the buffer matches the disk.
After that one, other unchanged events need to be ignored because the
buffer at that point can't match the disk.
Note that BU_UNCHANGED events are really just special cases of
sequence points. You could easily just combine them into one event.
It should be obvious by now that much editing would generate a LOT of
undo events. Unless your computer has an infinite amount of memory, you
need some rules about when to start throwing away events. Since
different people will have different ideas about th